/* vim:ft=objc:
 * $Id: AccountInfo.m 142 2005-05-22 21:18:07Z bmoore $
 *
 * Copyright (c) 2004, 2005  Branden J. Moore.
 *
 * This file is part of MacBiff, and is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * MacBiff is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MacBiff; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307 USA.
 *
 */

#import "AccountInfo.h"
#import "MacBiff.h"
#import "ConfigController.h"
#import <MacBiffPlugIn/debug.h>
#import <MacBiffPlugIn/PreferencesController.h>
#import <MacBiffPlugIn/plugins.h>
#import <MacBiffPlugIn/ActivityController.h>

#include <openssl/err.h>
#include <signal.h>
#include <pthread.h>

unsigned int checkingNow = 0;
extern MacBiff *macbiffController;
extern volatile sig_atomic_t user_pressed_stop;

@implementation AccountInfo

- (id) initWithAccount: (Account*) acct
	       andMenu: (NSMenu*) menu
	       andLock: (NSLock*) mLock
{
	if ( ( self = [super init] ) != Nil ) {
		timer = Nil;
		runLoop = Nil;
		pool = Nil;

		account = [acct retain];
		mainMenu = [menu retain];
		menuLock = [mLock retain];
		preSepMenuItem = [[NSMenuItem separatorItem] retain];
		headMenuItem = [[NSMenuItem alloc] initWithTitle: [account name]
				action: @selector(configureAccount:)
				keyEquivalent: @""];
		[headMenuItem setTarget: configController];
		[headMenuItem setRepresentedObject: self];
		menuItemsCount = 0;

		checkLock = [[NSLock alloc] init];
		checkInterval = [[[prefs account: [account name]]
					objectForKey: @"Check Interval"]
						unsignedIntValue];

	}
	return self;
}


- (void) dealloc
{
	if ( timer ) {
		[timer invalidate];
		[timer release];
	}
	[menuLock release];
	[mainMenu release];
	[preSepMenuItem release];
	[headMenuItem release];
	if ( account )
		[account release];


	[super dealloc];
}


- (void) _quit: (id) obj
{

	dprintf("About to invalidate.\n");
	[threadConnection invalidate];

	dprintf("About to release.\n");
	[threadConnection release];
	threadConnection = Nil;

	/* Cleanup any SSL error state */
	ERR_remove_state(0);

	dprintf("Thread %p (%s) quitting now, methinks\n",
			pthread_self(), [[account name] cString]);

	thread = NULL;
	[NSThread exit];
}


- (void) quit: (id) obj
{
	dprintf("Thread %p (%s) exiting, methinks\n",
			pthread_self(), [[account name] cString]);

	[timer invalidate];
	timer = Nil;

	[[NSRunLoop currentRunLoop] performSelector: @selector(_quit:)
			target: self
			argument: obj
			order: 0
			modes: [NSArray arrayWithObjects:
					NSDefaultRunLoopMode, nil]];
}


- (void) _activate: (id) obj
{
	NSLock *lk = obj;

	pool = [[NSAutoreleasePool alloc] init];
	dprintf("Thread %p activating.  Account: %s\n",
			pthread_self(), [[account name] cString]);

	thread = pthread_self();
	threadConnection = [[NSConnection alloc] init];
	if ( [threadConnection registerName: [account name]] ) {
		[threadConnection setRootObject: self];
	} else {
		alert("ThreadConnection failed to register.\n");
		[threadConnection release];
		threadConnection = Nil;
	}

	dprintf("Thread %p: Unlocking to let parent know connection ready\n",
			pthread_self());
	[lk unlock]; /* Let the parent know that the connection has been created */
	[menuLock lock];
	[mainMenu addItem: preSepMenuItem];
	[mainMenu addItem: headMenuItem];
	[menuLock unlock];


	timer = [NSTimer scheduledTimerWithTimeInterval: (checkInterval*60)
						 target: self
					       selector: @selector(_checkNow:)
					       userInfo: Nil
						repeats: YES];

	/* Must assign runLoop before calling [timer fire] because checkNow:
	 * will end up calling activate: if runLoop is not set.
	 */
	runLoop = [NSRunLoop currentRunLoop];
	[runLoop run];
}


- (void) activate: (id) obj
{
	if ( timer || runLoop ) {
		alert ("timer or runloop already set.\n");
		return;
	}

	NSLock *lk = [[NSLock alloc] init];
	[lk lock];
	dprintf("Parent(%p) Child about to spawn.\n", pthread_self());
	[NSThread detachNewThreadSelector: @selector(_activate:)
				 toTarget: self withObject: lk];

	/* Need to wait just a tick... */
	[lk lock];
	dprintf("Parent(%p) Child must be running... we unlocked.\n",
			pthread_self());
	[lk unlock];
	[lk release];
	selfProxy = [[NSConnection rootProxyForConnectionWithRegisteredName:
				[account name]
			host: Nil] retain];

	actView = [[ActivityViewController alloc] initWithStopSelector:
						@selector(stopCheck:)
					andGoSelector:
						@selector(checkNow:)
					target: self /*Proxy */
					userInfo: Nil];
	[activityControl registerActivityViewController: actView withName: [account name]];
	[account setActivityViewController: actView];

	/* Tell child to check */
	[selfProxy checkNow: Nil];
}


- (void) deactivate: (id) obj
{
	if ( !timer || !runLoop ) return;

	dprintf("Deactivating '%s'\n", [[account name] cString]);
	[selfProxy quit: Nil];

	[activityControl deregisterActivityViewController: actView];
	[account setActivityViewController: Nil];

	[menuLock lock];
	int startIndex = [mainMenu indexOfItem: preSepMenuItem];
	int i;
	for ( i = 0 ; i < menuItemsCount + 2 ; ++i ) {
		/* remove our old items */
		[mainMenu removeItemAtIndex: startIndex];
	}
	menuItemsCount = 0;
	[menuLock unlock];

	[selfProxy release];
	selfProxy = Nil;

	dprintf("Deactivated '%s'\n", [[account name] cString]);
}


- (Account*) account
{
	return account;
}


- (void) _checkNow: (id) obj
{
	int res;
	NSArray *actions;
	Action *act;

	if ( user_pressed_stop )  {
		return;
	}

	/* don't run if we aren't enabled */
	if ( ![account enabled] ) {
		NSString *title = [NSString stringWithFormat: @"%@ (Disabled)",
			 [account name]];
		[headMenuItem setTitle: title];
		[menuLock lock];
		int startIndex = [mainMenu indexOfItem: headMenuItem] + 1;
		for ( ; menuItemsCount ; menuItemsCount-- ) {
			/* remove our old items */
			[mainMenu removeItemAtIndex: startIndex];
		}
		[menuLock unlock];
		return;
	}

	if ( runLoop != [NSRunLoop currentRunLoop] ) {
		/* Make sure the right thread does this */
		dprintf("(%p) Calling the correct thread to check mail \n",
				pthread_self());
		[selfProxy checkNow: Nil];
		return;
	}

	dprintf("(%p) I should check now\n", pthread_self());
	if ( ![checkLock tryLock] ) return; /* checking already */
	checkingNow++; /* Post notification that we are now checking */
	[macbiffController performSelectorOnMainThread: @selector(setBarTitle:)
			withObject: Nil waitUntilDone: NO];

	/* Periodically clean up autorelease pool */
	[pool release];
	pool = [[NSAutoreleasePool alloc] init];

	/* Call pre-actions */
	if ( [[[prefs account: [account name]]
			objectForKey: @"Use Default Actions"] boolValue] ) {
		actions = [prefs defaultPreCheckActions];
	} else {
		actions = [[prefs account: [account name]]
				objectForKey: @"Pre-Check Actions"];
	}

	NSEnumerator *actionEnum = [actions objectEnumerator];
	NSString *actName = Nil;
	while ( (actName = [actionEnum nextObject]) ) {
		dprintf("Calling %s pre-act for acct %s\n",
			[actName cString], [[account name] cString]);
		/* CALL IT!!! */
		act = [actionPlugIns objectForKey: actName];
		[act preActionForAccount: account];
	}


	/* Check Mail */
	if ( (res = [account checkMail]) == 0 ) {
		/* Update Menu Item */
		NSString *title = [NSString stringWithFormat: @"%@ [%u/%d]",
			 [account name], [account unread], [account total]];
		NSArray *tMenu = [NSArray arrayWithArray:
				[account arrayOfMenuItems]];

		[headMenuItem setTitle: title];
		[menuLock lock];
		int startIndex = [mainMenu indexOfItem: headMenuItem] + 1;
		for ( ; menuItemsCount ; menuItemsCount-- ) {
			/* remove our old items */
			[mainMenu removeItemAtIndex: startIndex];
		}
		/* Add in the new items */
		NSEnumerator *mEnum = [tMenu objectEnumerator];
		NSMenuItem *item = Nil;
		while ( (item = [mEnum nextObject]) ) {
			[mainMenu insertItem: item
				atIndex: startIndex + menuItemsCount++];
		}
		[menuLock unlock];

		/* Call post-check actions */
		if ( [[[prefs account: [account name]] objectForKey:
				@"Use Default Actions"] boolValue] ) {
			actions = [prefs defaultPostCheckActions];
		} else{
			actions = [[prefs account: [account name]]
				objectForKey: @"Post-Check Actions"];
		}

		actionEnum = [actions objectEnumerator];
		actName = Nil;
		while ( (actName = [actionEnum nextObject]) ) {
			dprintf("Calling %s post-act for acct %s\n",
				[actName cString], [[account name] cString]);
			/* CALL IT!!! */
			act = [actionPlugIns objectForKey: actName];
			[act postActionForAccount: account];
		}

	} else {
		alert("Check Mail for account %s returned %d\n",
				[[account name] cString], res);
	}
	dprintf("Done with Checking Mail.  Going to update menubar\n");

	checkingNow--; /* Remove notification that we are checking */
	[macbiffController performSelectorOnMainThread: @selector(setBarTitle:)
			withObject: Nil waitUntilDone: NO];
	[checkLock unlock];
}


- (void) checkNow: (id) obj
{
	if ( user_pressed_stop ) return;

	if ( ![account enabled] ) return;

	if ( !runLoop || !timer ) {
		[self activate: Nil]; /* Will fire off thread */
		return;
	}

	dprintf("(%p) checkNow called, scheduling _checkNow\n", pthread_self());

	/* Schedule this to run, rather than run directly.  This
	 * allows the proxied connection to return quickly
	 */
	[[NSRunLoop currentRunLoop] performSelector: @selector(_checkNow:)
			target: self
			argument: obj
			order: 0
			modes: [NSArray arrayWithObjects:
					NSDefaultRunLoopMode,
					NSConnectionReplyMode, nil]];
}


- (void) stopCheck: (id) obj
{
	alert("Stop Check not yet implemented.\n");
	[account stopNow: Nil];
	pthread_kill( thread, SIGUSR2 );
}


- (NSString*) name
{
	return [account name];
}


- (void) reName: (NSString*) n
{
	[account setName: n];

	[menuLock lock];
	[headMenuItem setTitle: [account name]];
	[[headMenuItem menu] itemChanged: headMenuItem];
	[menuLock unlock];
}


- (BOOL) enabled
{
	return [account enabled];
}


- (unsigned) hash
{
	return [[account name] hash];
}


- (BOOL) isEqual: (id) obj
{
	return ([self hash] == [obj hash]);
}


@end
